// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: implementation of CImplItemset.
//   $Id$


#include "stdafx.h"
#include "itemset.h"
#include "select.h"
#include "SelectUI.h"
#include "choices.h"
#include "boundNode.h"
#include "model.h"

#include "implcontextualValue.h"
#include "isEqualNode.h"
#include "commonchild.h"

CImplItemset::CImplItemset()
	:m_pCopy(0)
	,m_pCaption(0)
	,m_pValue(0)
	,m_bRefreshing(false)
	,m_pParentList(0)
	,m_bSelected(false)
	,m_bDoneOnce(false)
	,m_pLastLabelList(0)
{
	m_pAlert = NULL;
}
CImplItemset::CImplItemset(CXFormsElementItemset* pXFEl)
	:m_pCopy(NULL)
	,m_pCaption(0)
	,m_pValue(NULL)
	,m_bRefreshing(false)
	,m_pParentList(0)
	,CImplNodesetBinding((CXFormsElementNodesetBinding*) pXFEl)
	,m_bSelected(false)
	,m_pLastLabelList(0)
{
	m_pAlert = NULL;
}

CImplItemset::~CImplItemset()
{
	m_ReliantControls.RemoveAll();

	if(m_pAlert) {
		delete m_pAlert;
		m_pAlert = 0;
	}

	delete m_pLastLabelList;

	if (m_pCaption)
	{
		delete m_pCaption;
		m_pCaption = 0;
	}
	
	if (m_pValue)
	{
		delete m_pValue;
		m_pValue = 0;
	}
	
	if (m_pCopy)
	{
		if(m_pCopy->m_pModel)
		{
			m_pCopy->m_pParent = 0;
		}
		else
		{
			delete m_pCopy;
		}
		m_pCopy = 0;
	}

	if(m_pModel)
		m_pModel->RemoveFormControl(this);

	if(m_pParent)
	{
		CImplSelection * pSel = dynamic_cast<CImplSelection *>(m_pParent);
		if(pSel)
			pSel->RemoveItem(this);
		else
		{
			CImplChoices * pChoices = dynamic_cast<CImplChoices *>(m_pParent);
			if(pChoices)
			{
				pChoices->RemoveItem(this);
			}
		}

	}
	else
	{
		CImplSelection * pSel = GetOwnerSelection();
		if(pSel)
			pSel->RemoveItem(this);

	}
}


HRESULT CImplItemset::init(CImpl *pParent)
{
	if(!m_sId || m_sId == L"")
	{
		m_sId.Empty();
		//must have an id for internal purposes,
		//	it is not an error for this to be without 
		//	an id.
		wchar_t sBuff[11];
		swprintf(sBuff,L"%X",(long)this);
		m_sId = sBuff; 
		// m_sId = CComBSTR(sBuff); // This results in the location of memory twice.
			
	}
	if(pParent && !m_pParent)
		m_pParent = pParent;
	return CImplNodesetBinding::init(pParent);
}


HRESULT CImplItemset::detachCaption()
{
	m_pCaption = 0;
	return S_OK;
}

HRESULT CImplItemset::detachAlert()
{
	m_pAlert = 0;
	return S_OK;
}

HRESULT CImplItemset::draw(BSTR const sDrawInside)
{
	HRESULT hr = E_FAIL;
	CImplSelection * pSel = GetOwnerSelection();
	if(pSel)
	{
		//Insert a placeholder, this will be overwritten by refresh().
		// HACK: Second m_sId is value. xf:select UI now returns a list of identifiers.
		BSTR bs = ::SysAllocString(L" ");
		hr = GetOwnerSelection()->drawItem(this, m_sId.m_str, bs, m_sId.m_str, sDrawInside, true);
		::SysFreeString(bs);
	}
	return hr;
}

bool Equivalent(CComBSTRList * LHS,CComBSTRList * RHS)
{
	
	bool bReturn = false;
	if(LHS && RHS)
	{
		bReturn = true;
		CComBSTRList::iterator iLHS = LHS->begin();
		CComBSTRList::iterator iRHS = RHS->begin();
		while(iLHS != LHS->end() && iRHS != RHS->end())
		{
			bReturn = (*iLHS == *iRHS);
			if(!bReturn)
				break;
			++iLHS;
			++iRHS;
		}
		if(iLHS != LHS->end() || iRHS != RHS->end())
			bReturn = false;
	}
	return bReturn;
}

HRESULT CImplItemset::refresh()
{

    	HRESULT hr = S_OK;
	//TODO: implement for copy.
	if(!m_bRefreshing)
	{
#ifdef OLD_SELECT_METHOD
		CImplSelection * pSel = GetOwnerSelection();
		if(pSel)
		{
			pSel->checkDrawn();
			
			long lSize = m_vertices.GetSize();
			if(lSize > 0)
			{
				//	loop through the bound nodes, executing
				//	the value and caption XPath statements in context.

				bool bClear = true;
				m_pXForms->putXPathIdentityResolver(GetIdResolver());
				CComBSTRList * pThisLabelList = new CComBSTRList;
				for(long l = 0;l < lSize;++l)
				{
					CBoundNode * pBoundNode = m_vertices.GetAt(l)->m_pInstanceNode;
					if(pBoundNode)
					{
						CComBSTR sCaption;
						CComPtr<IXMLDOMNode> pNode;
						hr = pBoundNode->getNode(&pNode);
						if(SUCCEEDED(hr))
						{
							if(SUCCEEDED(m_pCaption->GetValue(m_pModel,pNode,&sCaption)))
							{
								pThisLabelList->push_back(sCaption);
							}
						}
					}
				}
				m_pXForms->putXPathIdentityResolver(0);
				if(!Equivalent(pThisLabelList,m_pLastLabelList))
				{
					bool bClear = true;
					CComBSTRList::iterator iterator = pThisLabelList->begin();
					long l = 0;
					CStringW sIdTemplate;
					sIdTemplate.Format(L"%s[%%i]",m_sId);
					BSTR s2 = ::SysAllocString(L"");
					while(iterator!= pThisLabelList->end())
					{
						BSTR sCaption = (*iterator);
						CStringW sId;
						sId.Format(sIdTemplate,l);

						BSTR s1 = sId.AllocSysString();
						hr = ((CXFormsElementSelection*)GetOwnerSelection()->m_pXFormsElement)->AddItem(m_sId.m_str, sCaption, s1, s2, bClear);
						::SysFreeString(s1);
						if(FAILED(hr))
						{
							delete pThisLabelList;
							pThisLabelList = 0;
							break;
						}
						bClear = false;
						++iterator;
						++l;
					}
					::SysFreeString(s2);
					delete m_pLastLabelList;
					m_pLastLabelList = pThisLabelList;
					pThisLabelList =  0;
				}
				else
				{
					delete pThisLabelList;
					pThisLabelList = 0;
				}
			}
			else
			{
				//Blank it.
				CStringW sId;
				sId.Format(L"%s[0]",m_sId);
				BSTR s2 = ::SysAllocString(L"");
				BSTR s1 = sId.AllocSysString();
				hr = ((CXFormsElementSelection*)GetOwnerSelection()->m_pXFormsElement)->AddItem(m_sId.m_str, s2, s1, s2, true);
				::SysFreeString(s1);
				::SysFreeString(s2);
				
				//Discard last list.
				delete m_pLastLabelList;
				m_pLastLabelList = 0;

			}
			//refresh the selection, so that we know 
			//	whether it is still in range or not.
			m_bRefreshing = true;
			if(m_pLastLabelList)
				pSel->refresh();
			m_bRefreshing = false;
		}
#else

		long lSize = m_vertices.GetSize();
		long lArrSize = m_arrItems.GetSize();
	//TODO: make sure these bounds are correct.

		//remove any existing items above the itemset range	
		while(lArrSize > lSize && lArrSize > 1)
		{
			m_pParentList->RemoveItem(m_arrItems[--lArrSize]);
		}
		m_arrItems.SetSize(lSize);
		
		//fill the array up if it was smaller than it needed to be.
		CResolvedItem * pLastItem = m_arrItems[lArrSize-1];
		long lIxPrefix = m_lIx << 8;
		while(lArrSize < lSize)
		{
			pLastItem =  m_pParentList->InsertNewItem(pLastItem,lIxPrefix & lArrSize);
			m_arrItems[lArrSize] = pLastItem;
			++lArrSize;
		}
		
		if(lSize > 0)
		{
			//	loop through the bound nodes, executing
			//	the value and caption XPath statements in context.
			CComBSTR sCaptionRef;
			CComBSTR sValueRef;
			if (m_pCaption)
				sCaptionRef = m_pCaption->m_sRef;
			if (m_pValue)
				sValueRef = m_pValue->m_sRef;

			for(long l = 0;l < lSize;++l)
			{
				CBoundNode * pBoundNode = m_vertices.GetAt(l)->m_pInstanceNode;
				if(pBoundNode)
				{
					CComBSTR sCaption;
					CComBSTR sValue;
					CComPtr<IXMLDOMNode> pNode;
					hr = pBoundNode->getNode(&pNode);
					if(pNode)
					{
						m_pModel->getInstanceString(sCaptionRef, pNode, &sCaption);
						m_pModel->getInstanceString(sValueRef, pNode, &sValue);
					}
					//Set the value and caption, whether any success
					// was seen in fetching the values or not.
					CResolvedItem * pItem = m_arrItems[l];
					pItem->put_label(sCaption);
					pItem->put_value(sValue);
				}				
			}
		}
		else
		{
			//if size is zero, fill the zeroth item with nothing.
			CResolvedItem * p = m_arrItems[0];
			p->put_value((BSTR)0);
			p->put_label(0);
		}
#endif
	}
	FireStateEvents();
	return hr;
}


HRESULT CImplItemset::formControlInitialize()
{
	delete m_pLastLabelList;
	m_pLastLabelList = 0;
	return __super::formControlInitialize();
}



void CImplItemset::CreateResolvedCounterpart()
{
	//avoid excess calls to dynamic cast by storing a pointer to 
	// the parent's list.  This is only done in itemset because
	//	it makes multiple calls to insert and remove items.
	// The other classes don't call it so often (only once I think)

	m_pParentList = dynamic_cast<IResolvedItemContainer*>(m_pParent)->GetResolvedItemList();

	if(m_pParentList)
	{
		//In contrast to the others, this maintains an array 
		//	of items.  Remember, never delete item 0 or you
		//	will be unable to find the itemset's position in the 
		//	parental array.
		m_arrItems.SetAt(0,m_pParentList->InsertNewItem(0, m_lIx << 8));
	}

}


void CImplItemset::SetSelectionIndex(long lIndex)
{
	m_lIx = lIndex;
}


void CImplItemset::deselect()
{
	if(m_bSelected)
	{
		FireEvent(EVENT_DESELECTED);
		m_bSelected = false;
	}
}

void CImplItemset::select(const long lIndex)
{
	if(lIndex <= m_vertices.GetUpperBound())
	{	
		if (m_pValue)
		{
			CComBSTR sValue;
			GetValueAt(lIndex, &sValue);
			GetOwnerSelection()->appendValue(sValue);
		}
		else if(m_pCopy)
		{

			CComPtr<IXMLDOMNode> pCopyNode;
			GetCopyNodeAt(lIndex,&pCopyNode);
			GetOwnerSelection()->appendCopy(pCopyNode);
			//execute the copy expression in context of the first node inthe nodeset.
			//we must return a node in this case though.
			//This means that the argument for this
			//	should be a VARIANT (*) to allow for different types to be returned.
		}

		if(!m_bSelected)
		{
			FireEvent(EVENT_SELECTED);
			m_bSelected = true;
		}
	}
}




void CImplItemset::selectFrom(CList<VARIANT, VARIANT> &lstValues, CStringW &sList)
{
	POSITION pos = lstValues.GetHeadPosition();
	long lPre = lstValues.GetCount();
	CPtrList lstSelectedIndices;
	while(pos)
	{
		POSITION posTemp = pos;
		VARIANT v = lstValues.GetNext(pos);
		long lUBound = m_vertices.GetUpperBound();
		long l = 0;
		if(m_pValue && VT_BSTR == v.vt)
		{
			CComBSTR s;
			while(l <= lUBound)
			{
				if(!lstSelectedIndices.Find(reinterpret_cast<void*>(l),0))
				{
					GetValueAt(l, &s);
					if(s == v.bstrVal)
					{
						lstValues.RemoveAt(posTemp);
						CStringW sListItem;
						sListItem.Format(L"%s[%i] ",m_sId,l);
						sList += sListItem;
						::SysFreeString(v.bstrVal);
						lstSelectedIndices.AddTail(reinterpret_cast<void*>(l));
						break;
					}
					// Bug 211: Need to explicitly empty CComBSTR to prevent leaking memory each iteration
					s.Empty();
				}
				++l;

			}
		}
		else if(m_pCopy && VT_DISPATCH == v.vt)
		{
			CComQIPtr<IXMLDOMNode> pNode = v.pdispVal;
			CComPtr<IXMLDOMNode> pCopyNode;
			if(pNode)
			{
				while(l <= lUBound)
				{
					if(!lstSelectedIndices.Find(reinterpret_cast<void*>(l),0))
					{
						GetCopyNodeAt(l,&pCopyNode);
						if(::isEqualNode(pNode,pCopyNode,true))
						{
							lstValues.RemoveAt(posTemp);
							CStringW sListItem;
							sListItem.Format(L"%s[%i] ",m_sId,l);
							sList += sListItem;
							lstSelectedIndices.AddTail(reinterpret_cast<void*>(l));
							v.pdispVal->Release();
							break;					
						}
						pCopyNode.Release();
					}
					++l;
					
				}
			}
		}
	}

	long lPost = lstValues.GetCount();
	if(lPost < lPre)
		m_bSelected = true;
}

void CImplItemset::GetValueAt(long  lIndex, BSTR *sValue)
{
	CBoundNode * pBoundNode = m_vertices.GetAt(lIndex)->m_pInstanceNode;
	if(pBoundNode)
	{
		CComPtr<IXMLDOMNode>pNode;
		pBoundNode->getNode(&pNode);

		m_pXForms->putXPathIdentityResolver(GetIdResolver());
		m_pValue->GetValue(m_pModel,pNode,sValue);
		m_pXForms->putXPathIdentityResolver(0);
	}
}

void CImplItemset::GetCopyNodeAt(long  lIndex,IXMLDOMNode ** ppCopyNode)
{
	CBoundNode * pBoundNode = m_vertices.GetAt(lIndex)->m_pInstanceNode;
	if(pBoundNode)
	{
		CComPtr<IXMLDOMNode>pNode;
		pBoundNode->getNode(&pNode);
		//execute the value XPath in context of the first node in the nodeset.
		CComBSTR sCopyRef;
		sCopyRef = m_pCopy->m_sRef;
	
		
		m_pXForms->putXPathIdentityResolver(GetIdResolver());
		m_pModel->getInstanceNode(sCopyRef, pNode, false, ppCopyNode);
		m_pXForms->putXPathIdentityResolver(0);
	}
}

HRESULT CImplItemset::onvaluechanged(void)
{
	//refresh();
	return __super::onvaluechanged();
}